//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

///////////////////////////////////////////////////////////////////
//
// NOTE: PLEASE READ THE README.TXT FILE FOR INSTRUCTIONS ON
// COMPILING AND USAGE REQUIREMENTS.
//
// DESCRIPTION: NV20-specific (Geforce3) sample shader.
//				This shader can simultaneously display both decal 
//				(base color) and bump textures.
//
//  This shader builds on the foundation demonstrated in the 
//  hwUnlitShader.
//
//	Additionally, this sample demonstrates how to:
//		- Use vendor-specific extensions, namely vertex programs,
//		  texture shaders and register combiners, to achieve
//		  effects that are impossible in standard OpenGL.
//		- Convert height field bump format (used by Maya) into
//		  a normal map format, for real-time rendering.
//
//  Many parameters are easily customizable:
//		- The MNormalMapConverter::convertToNormalMap_InPlace()
//        bumpScale parameter is currently constant. You can change
//		  it to a different value to increase or decrease the 
//		  bumpiness.
//
//	PS: Thanks go to DAR from nVidia, for his help in making this
//      shader more robust. ;-)
//
///////////////////////////////////////////////////////////////////


// Uncomment the #ifdef below if you want to debug the vertex program by
// output a COL0 value that corresponds to an intermediate calculation.
// The only sane way we could find to debug that thing.
//#define DEBUGGING_VERTEX_PROGRAM 1

#ifdef WIN32
#pragma warning( disable : 4786 )		// Disable STL warnings.
#endif

#include <maya/MIOStream.h>
#include <math.h>

#include <maya/MString.h>
#include <maya/MPlug.h>
#include <maya/MDagPath.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MArrayDataHandle.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnLightDataAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MFnStringData.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MSceneMessage.h>

#include <maya/MPoint.h>
#include <maya/MMatrix.h>
#include <maya/MVector.h>
#include <maya/MEulerRotation.h>

// Include NVIDIA's helper libraries.  These libraries have
// copyright info in them so we cannot release them but we
// can use them to verify that the API works correctly.
//
#include <GL/gl.h>
#include <GL/glu.h>
#include <gl/glext.h>

#define GLH_EXT_SINGLE_FILE
#include "glh_extensions.h"
#undef GL_NV_vertex_array_range
#include "glh_genext.h"
#include "glh_obs.h"
using namespace glh;

#include "hwDecalBumpShader_NV20.h"
#include "ShadingConnection.h"

MTypeId hwDecalBumpShader_NV20::id( 0x00105441 );
/*static*/ const unsigned int hwDecalBumpShader_NV20::lookup_texture_size(256);


void hwDecalBumpShader_NV20::postConstructor( )
{
	setMPSafe(false);
}

// Static attribute instances.
//
MObject  hwDecalBumpShader_NV20::color;
MObject  hwDecalBumpShader_NV20::colorR;
MObject  hwDecalBumpShader_NV20::colorG;
MObject  hwDecalBumpShader_NV20::colorB;

MObject  hwDecalBumpShader_NV20::bump;
MObject  hwDecalBumpShader_NV20::bumpR;
MObject  hwDecalBumpShader_NV20::bumpG;
MObject  hwDecalBumpShader_NV20::bumpB;

MObject  hwDecalBumpShader_NV20::camera;
MObject  hwDecalBumpShader_NV20::cameraX;
MObject  hwDecalBumpShader_NV20::cameraY;
MObject  hwDecalBumpShader_NV20::cameraZ;

MObject  hwDecalBumpShader_NV20::light;
MObject  hwDecalBumpShader_NV20::lightX;
MObject  hwDecalBumpShader_NV20::lightY;
MObject  hwDecalBumpShader_NV20::lightZ;

MObject  hwDecalBumpShader_NV20::uCoord;
MObject  hwDecalBumpShader_NV20::vCoord;
MObject  hwDecalBumpShader_NV20::uvCoord;

MObject  hwDecalBumpShader_NV20::uBias;
MObject  hwDecalBumpShader_NV20::vBias;

MObject  hwDecalBumpShader_NV20::uvFilterSize;
MObject  hwDecalBumpShader_NV20::uvFilterSizeX;
MObject  hwDecalBumpShader_NV20::uvFilterSizeY;

MObject  hwDecalBumpShader_NV20::shininess;
MObject  hwDecalBumpShader_NV20::lightColor;
MObject  hwDecalBumpShader_NV20::lightColorR;
MObject  hwDecalBumpShader_NV20::lightColorG;
MObject  hwDecalBumpShader_NV20::lightColorB;

void hwDecalBumpShader_NV20::printGlError( const char *call )
{
    GLenum error;

	while( (error = glGetError()) != GL_NO_ERROR ) {
		assert(0);
	    cerr << call << ":" << error << " is " << (const char *)gluErrorString( error ) << "\n";
	}
}


// The Vertex Program for the Decal Bump effect.
//
// CONSTANTS:
//  0- 3  4x4 ModelView-Projection composite matrix
//  4- 7  4x4 ModelView  matrix
//  8-10  light amb/diff/spec
// 11     light dir vector (from surface to light)

// VERTEX REGISTERS:
// 0 - coord
// 1 - normal
// 2 - texcoord0
// 3 - texcoord1
// 4 - texcoord2 (binorm)

// REGISTERS:
// 4 = eye space vertex coordinate
// 5 = eye space tangent vector
// 6 = eye space binormal vector
// 7 = eye space normal vector			(VERIFIED)
// 8 = normalized eye space view vector (VERIFIED) (goes in the direction from vertex position to camera position)
// 9 = eye space half-angle vector
//
char vertexProgramString[] = 
	"!!VP1.0\n"

		// Multiply the vertex coords by the modelview-projection composite matrix,
		// to get clip space coordinates.
		"DP4   o[HPOS].x, c[0], v[0];"
		"DP4   o[HPOS].y, c[1], v[0];"
		"DP4   o[HPOS].z, c[2], v[0];"
		"DP4   o[HPOS].w, c[3], v[0];"

		// Multiply the vertex coords by the modelview matrix,
		// to get eye-space coordinates.
		"DP4   R4.x, c[4], v[0];"
		"DP4   R4.y, c[5], v[0];"
		"DP4   R4.z, c[6], v[0];"
		"DP4   R4.w, c[7], v[0];"

		// Multiply the normals by the modelview matrix,
		// resulting in eye-space normals.
		"DP3   R7.x, c[4], v[1];"
		"DP3   R7.y, c[5], v[1];"
		"DP3   R7.z, c[6], v[1];"
		// Re-normalize, in case the model-view matrix's is not a simple rotation.
		"DP3   R7.w, R7, R7;"			
		"RSQ   R7.w, R7.w;"
		"MUL   R7.xyz, R7, R7.w;"

		// Multiply the binormals by the modelview matrix,
		// resulting in eye-space binormals.
		"DP3   R6.x, c[4], v[4];"
		"DP3   R6.y, c[5], v[4];"
		"DP3   R6.z, c[6], v[4];"
		// Re-normalize the binormals.
		"DP3   R6.w, R6, R6;"
		"RSQ   R6.w, R6.w;"
		"MUL   R6.xyz, R6, R6.w;"

		// Build tangent: tangent = binormal x normal.
		"MUL   R5, R6.zxyw, R7.yzxw;"
		"MAD   R5, R6.yzxw, R7.zxyw, -R5;"
		// put the sign in the tangent.
		"MUL   R5.xyz, R5, v[4].w;"
		// Re-normalize the tangent.
		"DP3   R5.w, R5, R5;"
		"RSQ   R5.w, R5.w;"
		"MUL   R5.xyz, R5, R5.w;"
		
		// Calculate eye space view vector.
		// (In essence, a normalization of the inverse
		//  of the vertex coordinate in eye-space)
		"DP3   R8.w, R4, R4;"
		"RSQ   R8.w, R8.w;"
		"MUL   R8.xyz, R4, -R8.w;"

		// Calculate (and normalize) tangent space half-angle vector.
		"ADD   R9, R8, c[11];"
		"DP3   R9.w, R9, R9;"
		"RSQ   R9.w, R9.w;"
		"MUL   R9.xyz, R9, R9.w;"

		// Transform half-angle vector into tangent space.
		"DP3   o[TEX3].x, R5, R9;"
		"DP3   o[TEX3].y, R6, R9;"
		"DP3   o[TEX3].z, R7, R9;"

		// Transform light direction vector into tangent space.
		"DP3   o[TEX2].x, R5, c[11];"
		"DP3   o[TEX2].y, R6, c[11];"
		"DP3   o[TEX2].z, R7, c[11];"

#ifndef DEBUGGING_VERTEX_PROGRAM
		// Put diffuse lighting into color.
		"DP3 o[COL0], R7, c[11];"
#else
		// VISUALIZE NORMAL VECTOR IN EYE SPACE
		"MOV o[COL0], R7;"
#endif		
		// Copy texcoords.
		"MOV o[TEX0], v[2];"
		"MOV o[TEX1], v[3];"

        "END";


// Load the vertexProgram and fill in the necessary constants used in the vertex program.
//
void hwDecalBumpShader_NV20::loadVertexProgramGL()
{
	GLenum error = glGetError();
	assert(!error);

	// If the vertex program hasn't been created yet, do it now.
	// Note that Maya shares textures, display lists and vertex programs
	// between all viewports, so this only need to be done once.
	if (vertex_program_id == 0)
    {
	    glGenProgramsNV(1, &vertex_program_id);
        error = glGetError();
	    assert(!error);

	    // Attempt to load the program.
	    unsigned int length = strlen(vertexProgramString);
	    glLoadProgramNV(GL_VERTEX_PROGRAM_NV, vertex_program_id, length, (const GLubyte *) vertexProgramString);
	    error = glGetError();
	    assert(!error);

	    if (error)
	    {
		    // If an error occured, it's most likely due to a syntax or 
		    // logic error in the vertex program. The error position
		    // below will contain the index in the vertex program
		    // string that is faulty. See the NV_vertex_program
		    // extension specification for more details.
		    if (error == GL_INVALID_OPERATION)
		    {
			    int error_position = -2;

			    glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &error_position);
		    }
	    }
    }

    // Set up the constant values.
	//
	// CONSTANTS:
	//  0- 3  4x4 ModelView-Projection composite matrix
	//  4- 7  4x4 ModelView  matrix
	//  8-10  light amb/diff/spec
	// 11     light dir vector (from surface to light)
	//
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);
	glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 4, GL_MODELVIEW, GL_IDENTITY_NV);
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 8, 1, 1, 1, 1);
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1, 1, 1, 1);
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 10, 1, 1, 1, 1);
	glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, -lightRotation[0], -lightRotation[1], -lightRotation[2], 0);	// light dir...
}


// Illumination (diffuse and specular) lookup table
// (This function assumes that lookup_image and lookup_texture have been deallocated.)
//
void hwDecalBumpShader_NV20::make_lookup_texture()
{
	// Re-calculate the look-up texture, if the shininess value/scale has changed.
	//
	float	shininessScale = 1.0f;
	MPlug	plug(thisMObject(), shininess);
	
	// Get the shininess scaling factor
	//
	MStatus status = plug.getValue(shininessScale);
	if (!status)
	{
		status.perror("hwDecalBumpShader_NV20::bind plug.getValue.");
		return;
	}

	if ( shininessScale < 0.01f )
        shininessScale = 0.01f;
	if ( shininessScale > 1.0f )
        shininessScale = 1.0f;

	// shininess factor is between 20 and 180
	//
	float shininessValue = 1.0f;
	shininessValue = 200.0f * (1.0f - 0.8f * shininessScale);

	// Only recompute the lookup texture if the values have changed since the last bind.
	if (shininessValue == currentShininessValue && shininessScale == currentShininessScale)
		return;
	
	currentShininessValue = shininessValue;
	currentShininessScale = shininessScale;

	unsigned int imgsize = lookup_texture_size;
	float imgsizeM1 = (float) (imgsize - 1);

	// Allocate the lookup_image and lookup_texture.
	if (lookup_table == NULL)
        lookup_table = new unsigned char[imgsize*imgsize*2];
	if (lookup_texture == NULL)
        lookup_texture = new tex_object_2D;

	// Fill it up.
	unsigned char * ip = lookup_table;
	for(int j=0; j < imgsize; j++)
	{
		unsigned char a = (unsigned char) (shininessScale * (255.0 * pow((j/imgsizeM1), shininessValue)));
		for(int i=0; i < imgsize; i++)
		{
			*ip++ = (unsigned char) ((255.0 - 64) * (i/imgsizeM1)) + 64;
			*ip++ = a;
		}
	}

	fLookupTextureReprocessed = true;

	return;
}

void hwDecalBumpShader_NV20::bind_lookup_table()
{
	make_lookup_texture();

	lookup_texture->bind();

	if (fLookupTextureReprocessed)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, lookup_texture_size, lookup_texture_size, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, lookup_table); 
		fLookupTextureReprocessed = false;
	}

	lookup_texture->parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	lookup_texture->parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	lookup_texture->parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	lookup_texture->parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

// Initialize the necessary OpenGL extensions
//
void hwDecalBumpShader_NV20::init_ext(const char * ext)
{
	if(!glh_init_extension(ext))
        { cerr << "Failed to initialize " << ext << "!" << endl; exit(0); }
}

hwDecalBumpShader_NV20::hwDecalBumpShader_NV20()
{
	// Get an reference to the singleton texture cache.
	m_pTextureCache = MTextureCache::instance();

	init_ext("GL_ARB_multitexture");
	init_ext("GL_NV_register_combiners");
	init_ext("GL_NV_vertex_program");

	isDirectionalLight = true;	// light's rotation is connected to the lightRotation attr

	// Set the shininess and shininess scale to absurd values, so that the
	// look-up table automatically get recomputed during the first update.
	currentShininessValue = -1.0;
	currentShininessScale = -1.0;
	lookup_texture = NULL;
	lookup_table   = NULL;
	fLookupTextureReprocessed = false;

	// Initialize callbacks.
	fBeforeNewCB = 0;
	fBeforeOpenCB = 0;
	fBeforeRemoveReferenceCB = 0;
	fMayaExitingCB = 0;
	attachSceneCallbacks();

    vertex_program_id = 0;		// handle for the Vertex Program
    
	// The vertex program will get loaded during the first refresh.
	// We cannot do it here since it's not guaranteed that the GL
	// context will be current at construction or destruction time 
	// (although it generally is).
}

hwDecalBumpShader_NV20::~hwDecalBumpShader_NV20()
{
	detachSceneCallbacks();
}

void hwDecalBumpShader_NV20::releaseEverything()
{
	release_lookup_texture();

	if (vertex_program_id > 0)
	{
		glFinish();

		// Unbind any program.
		glBindProgramNV(GL_VERTEX_PROGRAM_NV, 0);
		
		// Delete the program used by this shader.
		glDeleteProgramsNV(1, &vertex_program_id);

		// For sanity.
        vertex_program_id = 0;
	}

	// Release the texture cache through refcounting.
	m_pTextureCache->release();
	if(!MTextureCache::getReferenceCount())
	{
		m_pTextureCache = 0;
	}
}

void hwDecalBumpShader_NV20::attachSceneCallbacks()
{
	fBeforeNewCB             = MSceneMessage::addCallback(MSceneMessage::kBeforeNew,
                                                          releaseCallback, this);
	fBeforeOpenCB            = MSceneMessage::addCallback(MSceneMessage::kBeforeOpen,
                                                          releaseCallback, this);
	fBeforeRemoveReferenceCB = MSceneMessage::addCallback(MSceneMessage::kBeforeRemoveReference,
                                                          releaseCallback, this);
	fMayaExitingCB           = MSceneMessage::addCallback(MSceneMessage::kMayaExiting,
                                                          releaseCallback, this);
}

/*static*/
void hwDecalBumpShader_NV20::releaseCallback(void* clientData)
{
	hwDecalBumpShader_NV20 *pThis = (hwDecalBumpShader_NV20*) clientData;
	pThis->releaseEverything();
}

void hwDecalBumpShader_NV20::detachSceneCallbacks()
{
	if (fBeforeNewCB)
		MMessage::removeCallback(fBeforeNewCB);
	if (fBeforeOpenCB)
		MMessage::removeCallback(fBeforeOpenCB);
	if (fBeforeRemoveReferenceCB)
		MMessage::removeCallback(fBeforeRemoveReferenceCB);
	if (fMayaExitingCB)
		MMessage::removeCallback(fMayaExitingCB);

	fBeforeNewCB = 0;
	fBeforeOpenCB = 0;
	fBeforeRemoveReferenceCB = 0;
	fMayaExitingCB = 0;
}

MStatus initializePlugin( MObject obj )
{ 
	MStatus   status;
	
	const MString UserClassify( "shader/surface/utility" );

	MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.5", "Any");
	status = plugin.registerNode( "hwDecalBumpShader_NV20", hwDecalBumpShader_NV20::id, 
			                      hwDecalBumpShader_NV20::creator, hwDecalBumpShader_NV20::initialize,
								  MPxNode::kHwShaderNode, &UserClassify );
	if (!status) {
		status.perror("registerNode");
		return status;
	}

	return MS::kSuccess;
}

MStatus uninitializePlugin( MObject obj )
{
	MStatus   status;
	
	MFnPlugin plugin( obj );

	plugin.deregisterNode( hwDecalBumpShader_NV20::id );
	if (!status) {
		status.perror("deregisterNode");
		return status;
	}

	return MS::kSuccess;
}


void * hwDecalBumpShader_NV20::creator()
{
    return new hwDecalBumpShader_NV20();
}

// Initialize the plug-in. Called once when the plug-in is loaded.
// This mostly involve creating attributes.
MStatus hwDecalBumpShader_NV20::initialize()
{
    MFnNumericAttribute nAttr; 
	MStatus status;
	MFnTypedAttribute sAttr; // For string attributes

    // Create input attributes

    colorR = nAttr.create( "colorR", "cr",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    colorG = nAttr.create( "colorG", "cg",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    colorB = nAttr.create( "colorB", "cb",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    color = nAttr.create( "color", "c", colorR, colorG, colorB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f, 0.5f, 0.5f);
    nAttr.setUsedAsColor(true);

    bumpR = nAttr.create( "bumpR", "c2r",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    bumpG = nAttr.create( "bumpG", "c2g",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    bumpB = nAttr.create( "bumpB", "c2b",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    bump = nAttr.create( "bump", "c2", bumpR, bumpG, bumpB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f, 1.0f, 1.0f);
    nAttr.setUsedAsColor(true);

    uCoord = nAttr.create( "uCoord", "u", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    vCoord = nAttr.create( "vCoord", "v", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);
 
    uvCoord = nAttr.create( "uvCoord","uv", uCoord, vCoord);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f, 0.5f );
    nAttr.setHidden(true);

    uBias = nAttr.create( "uBias", "bu", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    vBias = nAttr.create( "vBias", "bv", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.5f);

    uvFilterSizeX = nAttr.create( "uvFilterSizeX", "fsx", MFnNumericData::kFloat);
    nAttr.setStorable(false);
    nAttr.setReadable(true);
    nAttr.setWritable(true);
    nAttr.setHidden(true);

    uvFilterSizeY = nAttr.create( "uvFilterSizeY", "fsy", MFnNumericData::kFloat);
    nAttr.setStorable(false);
    nAttr.setReadable(true);
    nAttr.setWritable(true);
    nAttr.setHidden(true);

    uvFilterSize = nAttr.create("uvFilterSize","fs",uvFilterSizeX,uvFilterSizeY);
    nAttr.setStorable(false);
    nAttr.setReadable(true);
    nAttr.setWritable(true);
    nAttr.setHidden(true);

    lightX = nAttr.create( "lightX", "lgtx",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f);

    lightY = nAttr.create( "lightY", "lgty",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightZ = nAttr.create( "lightZ", "lgtz",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    light = nAttr.create( "light", "lgt", lightX, lightY, lightZ);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f, 1.0f, 1.0f);

    cameraX = nAttr.create( "cameraX", "camx",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f);

    cameraY = nAttr.create( "cameraY", "camy",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f);

    cameraZ = nAttr.create( "cameraZ", "camz",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    camera = nAttr.create( "camera", "cam", cameraX, cameraY, cameraZ);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f, 0.0f, 1.0f);

    shininess = nAttr.create( "shininess", "sn", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.5f);

    lightColorR = nAttr.create( "lightColorR", "lcr", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightColorG = nAttr.create( "lightColorG", "lcg", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightColorB = nAttr.create( "lightColorB", "lcb", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightColor = nAttr.create( "lightColor", "lc", lightColorR, lightColorG, lightColorB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f, 1.0f, 1.0f);
    nAttr.setUsedAsColor(true);


 // create output attributes here
	// outColor is the only output attribute and it is inherited
	// so we do not need to create or add it.
	//

 // Add the attributes here

    addAttribute(color);

    addAttribute(bump);

    addAttribute(uvCoord);

    addAttribute(uBias);
    addAttribute(vBias);

    addAttribute(uvFilterSize);

	addAttribute(light);
	addAttribute(camera);
    addAttribute(shininess);
    addAttribute(lightColor);

    attributeAffects (colorR, outColor);
    attributeAffects (colorG, outColor);
    attributeAffects (colorB, outColor);
    attributeAffects (color,  outColor);
    attributeAffects (bumpR, outColor);
    attributeAffects (bumpG, outColor);
    attributeAffects (bumpB, outColor);
    attributeAffects (bump,  outColor);
    attributeAffects (uCoord,  outColor);
    attributeAffects (vCoord,  outColor);
    attributeAffects (uvCoord, outColor);
    attributeAffects (uBias,   outColor);
    attributeAffects (vBias,   outColor);

	attributeAffects (lightX,	outColor);
	attributeAffects (lightY,	outColor);
	attributeAffects (lightZ,	outColor);
	attributeAffects (light,	outColor);
	attributeAffects (cameraX,	outColor);
	attributeAffects (cameraY,	outColor);
	attributeAffects (cameraZ,	outColor);
	attributeAffects (camera,	outColor);

    attributeAffects (shininess,   outColor);
    attributeAffects (lightColorR, outColor);
    attributeAffects (lightColorG, outColor);
    attributeAffects (lightColorB, outColor);
    attributeAffects (lightColor,  outColor);

    return MS::kSuccess;
}


// This function gets called by Maya to evaluate the shader.
// See "Writing a shading node plug-in" in the documentation
// for more information.
//
//
MStatus hwDecalBumpShader_NV20::compute(
const MPlug&      plug,
      MDataBlock& block ) 
{ 
    bool k;
    k = (plug == outColor)  ||
        (plug == outColorR) ||
        (plug == outColorG) ||
        (plug == outColorB);
    if( !k )
        return MS::kUnknownParameter;

    // set output color attribute
    MDataHandle outColorHandle = block.outputValue( outColor );
    MFloatVector& outColor = outColorHandle.asFloatVector();
    outColor.x = 1.0;
    outColor.y = 0.5;
    outColor.z = 0.5;
    outColorHandle.setClean();
    return MS::kSuccess;
}


// To get 3 float values from the node attribute
//
MStatus hwDecalBumpShader_NV20::getFloat3(MObject attr, float value[3])
{
	MStatus status = MS::kSuccess;

	// Get the attr to use
	//
	MPlug	plug(thisMObject(), attr);

	MObject object;
	status = plug.getValue(object);
	if (!status)
	{
		status.perror("hwDecalBumpShader_NV20::getFloat3 plug.getValue.");
		return status;
	}

	MFnNumericData data(object, &status);
	if (!status)
	{
		status.perror("hwDecalBumpShader_NV20::getFloat3 construct data.");
		return status;
	}

	status = data.getData(value[0], value[1], value[2]);
	if (!status)
	{
		status.perror("hwDecalBumpShader_NV20::getFloat3 get values.");
		return status;
	}

	return status;
}

// To get a string value from the node attribute
//
MStatus hwDecalBumpShader_NV20::getString(MObject attr, MString &str)
{
	MPlug	plug(thisMObject(), attr);
	MStatus status = plug.getValue( str );

	return status;
}

/* virtual */
MStatus	hwDecalBumpShader_NV20::bind(const MDrawRequest& request, M3dView& view)
{
	MStatus status;

	// Get the diffuse color
	//
	float diffuse_color[4];
	status = getFloat3(color, diffuse_color);
	diffuse_color[3] = 1.0;
	if (!status)
        return status;

	// Get the light color
	//
	float light_color[4];
	light_color[3] = 1.0f;
	status = getFloat3(lightColor, light_color);
	if (!status)
        return status;

	// Get the light direction (for directionalLight)
	//
	status = getFloat3(light, &lightRotation[0]);
	if (!status)
        return status;

	// Get the bumpScale value
	//
	float	bumpScaleValue = 2.0f;

	// Get the bumpMap type
	//
	bool	isHeightFieldMap = true;


	// Direction of the directional light
	//
	// Convert the light direction (which is assumed in originally be in world space, in euler coordinates) 
	// into an eye space vector.
	//
	double	scale = M_PI/180.0;		// Internal rotations are in radian and not in degrees
	MEulerRotation	lightRot( lightRotation[0] * scale, lightRotation[1] * scale, lightRotation[2] * scale );
	MVector	light_v = MVector(0, 0, -1).rotateBy( lightRot );	// WS light vector

	MDagPath camDag;
	view.getCamera(camDag);

	light_v = light_v * camDag.inclusiveMatrixInverse();
	lightRotation[0] = (float) light_v[0];
	lightRotation[1] = (float) light_v[1];
	lightRotation[2] = (float) light_v[2];


	// Get the camera position
	//
	status = getFloat3(camera, &cameraPos[0]);
	if (!status)
        return status;

	// Get the decal and bump map file names
	//
	MString decalName = "";
	MString bumpName  = "";

	ShadingConnection colorConnection(thisMObject(), request.multiPath().partialPathName(), "color");
	ShadingConnection bumpConnection (thisMObject(), request.multiPath().partialPathName(), "bump");

	// If the color attribute is ultimately connected to a file texture, find its filename.
	// otherwise use the default color texture.
	if (colorConnection.type() == ShadingConnection::TEXTURE &&
		colorConnection.texture().hasFn(MFn::kFileTexture))
	{
		// Get the filename of the texture.
		MFnDependencyNode textureNode(colorConnection.texture());
		MPlug filenamePlug( colorConnection.texture(), textureNode.attribute(MString("fileTextureName")) );
		filenamePlug.getValue(decalName);
	}

	// If the bump attribute is ultimately connected to a file texture, find its filename.
	// otherwise use the default bump texture.
	if (bumpConnection.type() == ShadingConnection::TEXTURE &&
		bumpConnection.texture().hasFn(MFn::kFileTexture))
	{
		// Get the filename of the texture.
		MFnDependencyNode textureNode(colorConnection.texture());
		MPlug filenamePlug( bumpConnection.texture(), textureNode.attribute(MString("fileTextureName")) );
		filenamePlug.getValue(bumpName);
	}

	// Fail safe quit
	//
	if (bumpName.length() == 0 ||
		decalName.length() == 0)
	{
		view.beginGL();
		glPushAttrib( GL_ALL_ATTRIB_BITS );		// This might be too conservative
		glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
		glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
		glEnable(GL_COLOR_MATERIAL);
		glColor4fv(diffuse_color);
		view.endGL();
		return MS::kSuccess;
	}

	view.beginGL();

	glPushAttrib( GL_ALL_ATTRIB_BITS );
	glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

    /* Starts Here... */
	glEnable(GL_TEXTURE_SHADER_NV);
	
	// stage 0 -- decal map
	glActiveTextureARB( GL_TEXTURE0_ARB );
	if(m_pTextureCache)
		m_pTextureCache->bind(colorConnection.texture(), MTexture::RGBA, false);
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);
	
    // stage 1 -- bumpped normal map
	glActiveTextureARB( GL_TEXTURE1_ARB );
	// We need to be able to pass the bumpScaleValue
	// to the texture cache and rebuild the bump or normal map
	if( isHeightFieldMap ) {
		// convert the HeightField to the NormalMap
		if(m_pTextureCache)
			m_pTextureCache->bind(bumpConnection.texture(), MTexture::NMAP, false);
	}
	else {
		if(m_pTextureCache)
			m_pTextureCache->bind(bumpConnection.texture(), MTexture::RGBA, false);
	}
   	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);
	
	// stage 2 -- dot product (diffuse component)
	glActiveTextureARB( GL_TEXTURE2_ARB );
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_NV);
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_EXPAND_NORMAL_NV);
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE1_ARB);

	// stage 3 -- dot product (specular component)
	glActiveTextureARB( GL_TEXTURE3_ARB );
	bind_lookup_table();	// 2D texture to get the diffuse and specular illumination
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_TEXTURE_2D_NV);
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_EXPAND_NORMAL_NV);
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE1_ARB);
	
	// With light color and intensity
	//
	glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, diffuse_color);
	glCombinerParameterfvNV(GL_CONSTANT_COLOR1_NV, light_color);

	// The register combiner will do the multiplication between
	// the illumination and the decal color
	//
	glEnable(GL_REGISTER_COMBINERS_NV);

	
#ifndef DEBUGGING_VERTEX_PROGRAM
	glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 2);
#else
	// For testing, only use one general register combiner.
	glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);
#endif

	float constColor0[4];
	constColor0[0] = constColor0[1] = constColor0[2] = constColor0[3] = 1.0;
	glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, constColor0);

#ifndef DEBUGGING_VERTEX_PROGRAM
	// Combiner stage 0 does the illumination modulation on the surface decal color
	//
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);

	glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE1_NV, 
					   GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);

	// Combiner stage 1, modulate the surface color by the light color
	//
	glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);

	glCombinerOutputNV(GL_COMBINER1_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE1_NV, 
					   GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);
#else
	// Simplified register combiners to help debugging vertex program.
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
	glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);

	glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, 
					   GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);
#endif // DEBUGGING_VERTEX_PROGRAM

	// The final Combiner just pass through
	//
	glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);

	view.endGL();

	return MS::kSuccess;
}


/* virtual */
MStatus	hwDecalBumpShader_NV20::unbind(const MDrawRequest& request,
			   M3dView& view)
{
	view.beginGL();
	
	glDisable(GL_REGISTER_COMBINERS_NV);
	glDisable(GL_TEXTURE_SHADER_NV);

	glActiveTextureARB( GL_TEXTURE0_ARB );

	glPopClientAttrib();
	glPopAttrib();

	view.endGL();

	return MS::kSuccess;
}

// Compute array of binormals. Return the pointer to the array if succesful,
// NULL otherwise.
float* hwDecalBumpShader_NV20::computeBinormals(int indexCount,
										   const unsigned int * indexArray,
										   int vertexCount,
										   const float* vertexArray, 
										   const float* normalArray, 
										   const float* texCoordArray)
{
	// Allocate the array of binormals.
	float *biNormalArray = new float[3*vertexCount];

	// Allocate space for a triangle of vertices, textures, normals, binormals.
	// This triangle will be used to compute the binormal vector.
	//
	const float *	v[3];	// Vertex		(x,y,z)
	const float *	t[3];	// Texture		(s,t)
	const float *	n[3];	// Normal		(x,y,z)
	float *			b[3];	// BiNormal		(x,y,z)
	
	for (int i = 0; i < indexCount; i += 3)
	{
		int i0 = indexArray[i+0];
		int i1 = indexArray[i+1];
		int i2 = indexArray[i+2];
		
		// Get the xyz coords of the corners of the triangle.
		//
		v[0] = vertexArray + 3 * i0;
		v[1] = vertexArray + 3 * i1;
		v[2] = vertexArray + 3 * i2;
		
		// Get the st coords of the corners of the triangle.
		//
		t[0] = texCoordArray + 2 * i0;
		t[1] = texCoordArray + 2 * i1;
		t[2] = texCoordArray + 2 * i2;
		
		// Get the normals at the corners of the triangle.
		//
		n[0] = normalArray + 3 * i0;
		n[1] = normalArray + 3 * i1;
		n[2] = normalArray + 3 * i2;
		
		// Get pointers to the binormal vectors.
		//
		b[0] = biNormalArray + 3 * i0;
		b[1] = biNormalArray + 3 * i1;
		b[2] = biNormalArray + 3 * i2;
		
		// *********************************************
		// ******** compute the binormal vector ********
		// *********************************************
		vec3f	plane[3];
		vec3f	c0, c1, c2;
		vec3f	s0, s1;
		
		// Calculate plane equations for the planes defined
		// by the (x, s, t), (y, s, t), and (z, s, t) coords.
		int idx;
		for (idx = 0; idx < 3; ++idx)
		{
			// Set up the three corners
			c0.set_value(v[0][idx], t[0][0], t[0][1]);
			c1.set_value(v[1][idx], t[1][0], t[1][1]);
			c2.set_value(v[2][idx], t[2][0], t[2][1]);
			
			// Calculate two sides
			s0 = c0 - c2;
			s1 = c1 - c2;
			
			// Calculate the normal of the plane
			plane[idx] = s1.cross(s0);
		}
		
		// Now solve for the texture gradients dsdx, dsty, dsdt, ...
		vec3f ds, dt, dn;
		
		for (idx = 0; idx < 3; ++idx)
		{
			ds[idx] = -plane[idx][1]/plane[idx][0];
			dt[idx] = -plane[idx][2]/plane[idx][0];
		}

		dt.normalize();
		dn = ds.cross(dt);
	
		// Make sure that our computed normal vector points in the
		// same direction as the input normal vector.
		vec3f normal(n[0]);
		if (normal.dot(dn) < 0)
		{
			// They pointed in different directions, negate
			ds.negate();
			dt.negate();
			dn.negate();
		}
		
		// Compute the biNormal vector and store them in the biNormal array
		for (idx = 0; idx < 3; ++idx)
		{
			vec3f vn = n[idx];
			vec3f vb = vn.cross( dt );
			vb.normalize();
			
			for( int j = 0; j < 3; ++j )
				b[idx][j] = vb[j];
		}
	}

	return biNormalArray;
}


/* virtual */
MStatus	hwDecalBumpShader_NV20::geometry( const MDrawRequest& request,
								M3dView& view,
							    int prim,
								unsigned int writable,
								int indexCount,
								const unsigned int * indexArray,
								int vertexCount,
								const int * vertexIDs,
								const float * vertexArray,
								int normalCount,
								const float ** normalArrays,
								int colorCount,
								const float ** colorArrays,
								int texCoordCount,
								const float ** texCoordArrays)
{
	// We assume triangles here.
	//
	if (prim != GL_TRIANGLES)
		return MS::kSuccess;		

	view.beginGL();

	// Bind the vertex program. Note that this will automatically
	// create and compile the vertex program, the first time
	// this function gets called.
	//
    loadVertexProgramGL();

	// Bind and enable the vertex program
	//
	glBindProgramNV(GL_VERTEX_PROGRAM_NV, vertex_program_id);
	glEnable(GL_VERTEX_PROGRAM_NV);

	// VERTEX REGISTERS (Attributes):
	// 0 - coord
	// 1 - normal
	// 2 - texcoord0 (decal 2D texture)
	// 3 - texcoord1 (bump 2D texture)
	// 4 - texcoord2 (binormal vector (in object space))
	glVertexAttribPointerNV( 0, 3, GL_FLOAT, 0, vertexArray );
	glVertexAttribPointerNV( 1, 3, GL_FLOAT, 0, normalArrays[0] );
	glVertexAttribPointerNV( 2, 2, GL_FLOAT, 0, texCoordArrays[0] );
	glVertexAttribPointerNV( 3, 2, GL_FLOAT, 0, texCoordArrays[0] );
	glVertexAttribPointerNV( 4, 3, GL_FLOAT, 0, normalArrays[2] );

	glEnableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
	glEnableClientState( GL_VERTEX_ATTRIB_ARRAY1_NV );
	glEnableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );
	glEnableClientState( GL_VERTEX_ATTRIB_ARRAY3_NV );
	glEnableClientState( GL_VERTEX_ATTRIB_ARRAY4_NV );

	glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indexArray);

	glDisableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
	glDisableClientState( GL_VERTEX_ATTRIB_ARRAY1_NV );
	glDisableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );
	glDisableClientState( GL_VERTEX_ATTRIB_ARRAY3_NV );
	glDisableClientState( GL_VERTEX_ATTRIB_ARRAY4_NV );

	glDisable(GL_VERTEX_PROGRAM_NV);

	glClientActiveTextureARB(GL_TEXTURE0_ARB);

	view.endGL();

	return MS::kSuccess;
}

/* virtual */
int		hwDecalBumpShader_NV20::normalsPerVertex()
{
	return 3;
}

/* virtual */
int		hwDecalBumpShader_NV20::texCoordsPerVertex()
{
	return 1;
}

// Release the lookup texture/image.
void hwDecalBumpShader_NV20::release_lookup_texture()
{
	if (lookup_table)
	{
		delete lookup_table;
		lookup_table = NULL;
	}
	
	if (lookup_texture)
	{
		delete lookup_texture;
		lookup_table = NULL;
	}
}
